package com.fast.orm.mapper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.*;
import cn.hutool.log.LogFactory;
import com.fast.orm.cache.DataCacheType;
import com.fast.orm.cache.FastRedisCache;
import com.fast.orm.cache.FastStaticCache;
import com.fast.orm.config.FastAttributes;
import com.fast.orm.config.Null;
import com.fast.orm.generator.util.DbUtils;
import com.fast.orm.many.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 对象映射创建工具
 *
 * @author 张亚伟 https://github.com/kaixinzyw
 */
public class TableMapperUtil {

    /**
     * 对象映射信息<类名,映射信息>
     */
    private static final Map<String, TableMapper> tableMappers = new HashMap<>();

    /**
     * 获取类映射关系
     *
     * @param clazz 类信息
     * @return 结果
     */
    public static TableMapper getTableMappers(Class clazz) {
        return getTableMappers(clazz, null);
    }

    /**
     * 获取类映射关系
     *
     * @param clazz 类信息
     * @return 结果
     */
    public static TableMapper getTableMappers(Class clazz, String tableName) {
        TableMapper tableMapper = tableMappers.get(clazz.getName());
        if (tableMapper == null) {
            tableMapper = createRowMapper(clazz, tableName);
            LogFactory.get(TableMapper.class).info("加载映射:{}", clazz.getName());
        }
        return tableMapper;
    }

    /**
     * 创建类映射关系
     *
     * @param clazz 类信息
     * @param <T>   操作的类泛型
     * @return 创建结果
     */
    private static synchronized <T> TableMapper createRowMapper(Class<T> clazz, String tableName) {
        if (tableMappers.get(clazz.getName()) != null) {
            return tableMappers.get(clazz.getName());
        }
        TableMapper tableMapper = new TableMapper();
        tableMapper.setClassName(clazz.getSimpleName());
        tableMapper.setObjClass(clazz);
        tableMappers.put(clazz.getName(), tableMapper);

        if (StrUtil.isBlank(tableName)) {
            if (clazz.isAnnotationPresent(TableName.class)) {
                TableName annotation = clazz.getAnnotation(TableName.class);
                tableMapper.setTableName(annotation.value());
                tableMapper.setTableAlias(annotation.value());
            } else {
                if (clazz.isAnnotationPresent(Table.class)) {
                    Table annotation = clazz.getAnnotation(Table.class);
                    tableMapper.setTableName(annotation.name());
                    tableMapper.setTableAlias(annotation.name());
                } else {
                    String toUnderlineCase = StrUtil.toUnderlineCase(tableMapper.getClassName());
                    tableMapper.setTableName(toUnderlineCase);
                    tableMapper.setTableAlias(toUnderlineCase);
                }
            }
        } else {
            tableMapper.setTableName(tableName);
            tableMapper.setTableAlias(tableName);
        }

        if (clazz.isAnnotationPresent(TableAlias.class)) {
            TableAlias annotation = clazz.getAnnotation(TableAlias.class);
            tableMapper.setTableAlias(annotation.value());
        }
        if (FastAttributes.isOpenCache) {
            if (clazz.isAnnotationPresent(FastRedisCache.class)) {
                FastRedisCache fastRedisCache = clazz.getAnnotation(FastRedisCache.class);
                tableMapper.setCacheType(DataCacheType.RedisCache);
                if (fastRedisCache.value() != 0L) {
                    tableMapper.setCacheTime(fastRedisCache.value());
                    tableMapper.setCacheTimeType(fastRedisCache.cacheTimeType());
                } else if (fastRedisCache.cacheTime() != 0L) {
                    tableMapper.setCacheTime(fastRedisCache.cacheTime());
                    tableMapper.setCacheTimeType(fastRedisCache.cacheTimeType());
                } else {
                    tableMapper.setCacheTime(FastAttributes.defaultCacheTime);
                    tableMapper.setCacheTimeType(FastAttributes.defaultCacheTimeType);
                }
                tableMapper.setOpenCache(Boolean.TRUE);
            } else if (clazz.isAnnotationPresent(FastStaticCache.class)) {
                FastStaticCache fastStaticCache = clazz.getAnnotation(FastStaticCache.class);
                tableMapper.setCacheType(DataCacheType.StaticCache);
                if (fastStaticCache.value() != 0L) {
                    tableMapper.setCacheTime(fastStaticCache.value());
                    tableMapper.setCacheTimeType(fastStaticCache.cacheTimeType());
                } else if (fastStaticCache.cacheTime() != 0L) {
                    tableMapper.setCacheTime(fastStaticCache.cacheTime());
                    tableMapper.setCacheTimeType(fastStaticCache.cacheTimeType());
                } else {
                    tableMapper.setCacheTime(FastAttributes.defaultCacheTime);
                    tableMapper.setCacheTimeType(FastAttributes.defaultCacheTimeType);
                }
                tableMapper.setOpenCache(Boolean.TRUE);
            }
        }
        Field[] fields = FieldUtils.getAllFields(clazz);
        List<String> fieldNames = new ArrayList<>();
        LinkedHashMap<String, Class> fieldTypes = new LinkedHashMap<>();
        LinkedHashMap<String, String> fieldTableNames = new LinkedHashMap<>();
        LinkedHashMap<String, String> selectShowField = new LinkedHashMap<>();
        LinkedHashMap<String, String> tableFieldNames = new LinkedHashMap<>();
        StrBuilder selectAllShowField = new StrBuilder();
        StrBuilder selectPrefixAllShowField = new StrBuilder();
        //获取主键
        for (Field field : fields) {
            if (field.isAnnotationPresent(NotQuery.class)) {
                continue;
            }
            boolean isId = field.isAnnotationPresent(Id.class);
            if (isId || "id".equals(field.getName())) {
                if (tableMapper.getPrimaryKeyTableField() == null) {
                    String tabFieldName = getTabFieldName(field);
                    tableMapper.setPrimaryKeyField(field.getName());
                    tableMapper.setPrimaryKeyTableField(tabFieldName);
                    tableMapper.setPrimaryKeyClass(field.getType());
                    if (field.getType() == String.class) {
                        tableMapper.setPrimaryKeyType(PrimaryKeyType.OBJECTID);
                    } else {
                        tableMapper.setPrimaryKeyType(PrimaryKeyType.AUTO);
                    }
                    String columnName = tableMapper.getTableName() + "_" + tabFieldName;
                    tableMapper.setDefaultJoinFieldName(StrUtil.toCamelCase(columnName));
                    tableMapper.setDefaultJoinColumnName(columnName);
                }
                break;
            }
        }
        for (Field field : fields) {
            tableMapper.addFieldFieldMap(field.getName(), field);
            if (field.isAnnotationPresent(NotQuery.class) ) {
                continue;
            }
            if ("serialVersionUID".equals(field.getName()) || StrUtil.equals(field.getName(), "fastExample") ||
                    CollUtil.contains(FastAttributes.ruleOutFieldList, field.getName()) || fieldTableNames.get(field.getName()) != null) {
                continue;
            }
            if (field.isAnnotationPresent(AutoQuery.class)) {
                AutoQuery autoQuery = field.getAnnotation(AutoQuery.class);
                packageAutoQuery(tableMapper, field, autoQuery);
                if (field.isAnnotationPresent(JoinQuery.class)) {
                    joinQuery(tableMapper, selectShowField, selectAllShowField, selectPrefixAllShowField, field);
                }
                continue;
            }
            if (field.isAnnotationPresent(JoinQuery.class)) {
                joinQuery(tableMapper, selectShowField, selectAllShowField, selectPrefixAllShowField, field);
                continue;
            }

            String tabFieldName = getTabFieldName(field);
            if (FastAttributes.isOpenLogicDelete && field.getName().equals(FastAttributes.deleteFieldName)) {
                tableMapper.setLogicDelete(Boolean.TRUE);
            }
            if (FastAttributes.isAutoSetCreateTime && field.getName().equals(FastAttributes.createTimeFieldName)) {
                tableMapper.setAutoSetCreateTime(Boolean.TRUE);
            }
            if (FastAttributes.isAutoSetUpdateTime && field.getName().equals(FastAttributes.updateTimeFieldName)) {
                tableMapper.setAutoSetUpdateTime(Boolean.TRUE);
            }
            fieldNames.add(field.getName());
            fieldTypes.put(field.getName(), field.getType());
            fieldTableNames.put(field.getName(), tabFieldName);
            tableFieldNames.put(tabFieldName, field.getName());
            String tableFieldName = "`" + tabFieldName + "`";
            selectShowField.put(field.getName(), tableFieldName);
            selectAllShowField.append(tableFieldName).append(",");
            selectPrefixAllShowField.append(StrUtil.strBuilder(tableMapper.getTableAlias(), StrUtil.DOT, tableFieldName, ","));
        }
        tableMapper.setFiledShowColumnMap(selectShowField);
        tableMapper.setFieldNames(fieldNames);
        tableMapper.setFieldTypes(fieldTypes);
        tableMapper.setFieldColumnMap(fieldTableNames);
        tableMapper.setColumnFieldMap(tableFieldNames);
        if (StrUtil.isNotBlank(selectAllShowField) && selectAllShowField.length() > 1) {
            tableMapper.setQueryRow(selectAllShowField.subString(0, selectAllShowField.length() - 1));
            tableMapper.setQueryPrefixRow(selectPrefixAllShowField.subString(0, selectPrefixAllShowField.length() - 1));
        }
        return tableMapper;
    }

    private static void joinQuery(TableMapper tableMapper, LinkedHashMap<String, String> selectShowField, StrBuilder selectAllShowField, StrBuilder selectPrefixAllShowField, Field field) {
        JoinQuery annotation = field.getAnnotation(JoinQuery.class);
        if (ClassUtil.isBasicType(field.getType()) || DbUtils.rowType.contains(field.getType().getSimpleName())) {
            String tableAliasName;
            if (StrUtil.isNotBlank(annotation.tableAlias())) {
                tableAliasName = annotation.tableAlias();
            } else {
                if (Null.class.equals(annotation.value())) {
                    return;
                } else {
                    tableAliasName = getTableMappers(annotation.value()).getTableAlias();
                }
            }
            String tabFieldName = getTabFieldName(field);
            String tableFieldName;
            if (StrUtil.isNotBlank(annotation.columnName())) {
                tableFieldName = "`" + annotation.columnName() + "`" + " AS " + field.getName();
                tableMapper.addColumnAliasMap(tabFieldName, annotation.columnName());
            } else {
                tableFieldName = "`" + tabFieldName + "`";
            }
            selectShowField.put(field.getName(), tableFieldName);
            selectAllShowField.append(tableFieldName).append(StrUtil.COMMA);
            selectPrefixAllShowField.append(StrUtil.strBuilder(tableAliasName, StrUtil.DOT, tableFieldName, ","));
            tableMapper.addTableAliasFieldMap(tableAliasName, tableMapper.getColumnAliasMap().get(tabFieldName) != null ? tableMapper.getColumnAliasMap().get(tabFieldName) : tabFieldName, field.getName());
        } else {
            FastJoinQueryInfo info = new FastJoinQueryInfo();
            info.setFieldName(field.getName());
            info.setCollectionType(Collection.class.isAssignableFrom(field.getType()));
            TableMapper joinMappers;
            String joinTableAlias;
            if (info.getCollectionType()) {
                joinMappers = getTableMappers(TypeUtil.getClass(TypeUtil.getTypeArgument(
                        TypeUtil.getReturnType(ReflectUtil.getMethod(tableMapper.getObjClass(), StrBuilder.create("get", StringUtils.capitalize(field.getName())).toString())))));
            } else {
                joinMappers = getTableMappers(field.getType());
            }
            info.setJoinPrimaryKeyColumnName(joinMappers.getPrimaryKeyTableField());
            if (StrUtil.isNotBlank(annotation.tableAlias())) {
                info.setJoinTableAlias(annotation.tableAlias());
                joinTableAlias = annotation.tableAlias();
            } else {
                if (!Null.class.equals(annotation.value())) {
                    TableMapper tableMappers = getTableMappers(annotation.value());
                    info.setJoinTableAlias(tableMappers.getTableAlias());
                    joinTableAlias = tableMappers.getTableAlias();
                } else {
                    joinTableAlias = joinMappers.getTableAlias();
                }
            }
            if (StrUtil.isNotBlank(annotation.columnName())) {
                info.setJoinColumnName(annotation.columnName());
                info.setJoinFieldName(FastAttributes.isToCamelCase ? StrUtil.toCamelCase(annotation.columnName()) : annotation.columnName());
            }
            tableMapper.addFastJoinQueryInfoMap(joinTableAlias, info);
            tableMapper.putFastJoinQueryInfoMap(joinMappers.getFastJoinQueryInfoMap());
        }
    }

    private static void packageAutoQuery(TableMapper tableMapper, Field field, AutoQuery autoQuery) {
        Class mappersClass;
        if (Null.class.equals(autoQuery.value())) {
            if (ClassUtil.isBasicType(field.getType()) || DbUtils.rowType.contains(field.getType().getSimpleName())) {
                throw new RuntimeException("映射异常 基本类型使用AutoQuery必须设置value(查询类的映射)");
            }
            if (Collection.class.isAssignableFrom(field.getType())) {
                mappersClass = TypeUtil.getClass(TypeUtil.getTypeArgument(
                        TypeUtil.getReturnType(ReflectUtil.getMethod(tableMapper.getObjClass(), StrBuilder.create("get", StringUtils.capitalize(field.getName())).toString()))));
            } else {
                mappersClass = field.getType();
            }
        } else {
            mappersClass = autoQuery.value();
        }

        TableMapper mappers = getTableMappers(mappersClass);
        AutoQueryInfo info = new AutoQueryInfo();
        info.setQueryTableMapper(mappers);
        info.setFieldName(field.getName());
        info.setCollectionType(Collection.class.isAssignableFrom(field.getType()));
        info.setBasicType(DbUtils.rowType.contains(field.getType().getSimpleName()));
        info.setValueFieldName(StrUtil.isNotBlank(autoQuery.queryField()) ? autoQuery.queryField() : field.getName());
        info.setValueColumnName(getTabFieldName(info.getValueFieldName()));
        info.setAutoQueryInfoList(mappers.getAutoQueryInfoList());
        boolean joinDirection = true;
        if (autoQuery.thisField().length() > 0) {
            info.setThisColumnName(mappers.getFieldColumnMap().get(autoQuery.thisField()));
            info.setThisFieldName(autoQuery.thisField());
        } else {
            if (mappers.getFieldNames().contains(tableMapper.getDefaultJoinFieldName())) {
                info.setThisFieldName(tableMapper.getDefaultJoinFieldName());
                info.setThisColumnName(tableMapper.getDefaultJoinColumnName());
            } else {
                joinDirection = false;
                info.setThisFieldName(mappers.getPrimaryKeyField());
                info.setThisColumnName(mappers.getPrimaryKeyTableField());
            }
        }
        if (autoQuery.targetField().length() > 0) {
            String columnName = FastAttributes.isToCamelCase ? StrUtil.toUnderlineCase(autoQuery.targetField()) : autoQuery.targetField();
            info.setTargetColumnName(columnName);
            info.setTargetFieldName(autoQuery.targetField());
        } else {
            if (joinDirection) {
                info.setTargetColumnName(tableMapper.getPrimaryKeyTableField());
                info.setTargetFieldName(tableMapper.getPrimaryKeyField());
            } else {
                info.setTargetColumnName(mappers.getDefaultJoinColumnName());
                info.setTargetFieldName(mappers.getDefaultJoinFieldName());
            }
        }
        tableMapper.getAutoQueryInfoList().add(info);
    }

    private static String getTabFieldName(Field field) {
        String tabFieldName;
        boolean isColumn = field.isAnnotationPresent(Column.class);
        if (isColumn) {
            Column name = field.getAnnotation(Column.class);
            tabFieldName = name.name();
        } else {
            if (FastAttributes.isToCamelCase) {
                tabFieldName = StrUtil.toUnderlineCase(field.getName());
            } else {
                tabFieldName = field.getName();
            }
        }
        return tabFieldName;
    }

    private static String getTabFieldName(String fieldName) {
        if (FastAttributes.isToCamelCase) {
            return StrUtil.toUnderlineCase(fieldName);
        } else {
            return fieldName;
        }
    }

}
